// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <re2/re2.h>

#include <pollux/common/config/config.h>

namespace kumo::pollux::config {
    double toBytesPerCapacityUnit(CapacityUnit unit) {
        switch (unit) {
            case CapacityUnit::BYTE:
                return 1;
            case CapacityUnit::KILOBYTE:
                return exp2(10);
            case CapacityUnit::MEGABYTE:
                return exp2(20);
            case CapacityUnit::GIGABYTE:
                return exp2(30);
            case CapacityUnit::TERABYTE:
                return exp2(40);
            case CapacityUnit::PETABYTE:
                return exp2(50);
            default:
                POLLUX_USER_FAIL("Invalid capacity unit '{}'", static_cast<int>(unit));
        }
    }

    CapacityUnit valueOfCapacityUnit(const std::string &unitStr) {
        std::stringstream ss;
        for (const char c: unitStr) {
            ss << static_cast<char>(std::tolower(c));
        }
        const auto lowerUnitStr = ss.str();
        if (lowerUnitStr == "b") {
            return CapacityUnit::BYTE;
        }
        if (lowerUnitStr == "kb") {
            return CapacityUnit::KILOBYTE;
        }
        if (lowerUnitStr == "mb") {
            return CapacityUnit::MEGABYTE;
        }
        if (lowerUnitStr == "gb") {
            return CapacityUnit::GIGABYTE;
        }
        if (lowerUnitStr == "tb") {
            return CapacityUnit::TERABYTE;
        }
        if (lowerUnitStr == "pb") {
            return CapacityUnit::PETABYTE;
        }
        POLLUX_USER_FAIL("Invalid capacity unit '{}'", unitStr);
    }

    uint64_t toCapacity(const std::string &from, CapacityUnit to) {
        static const RE2 kPattern(R"(^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$)");
        double value;
        std::string unit;
        if (!RE2::FullMatch(from, kPattern, &value, &unit)) {
            POLLUX_USER_FAIL("Invalid capacity string '{}'", from);
        }

        return value *
               (toBytesPerCapacityUnit(valueOfCapacityUnit(unit)) /
                toBytesPerCapacityUnit(to));
    }

    std::chrono::duration<double> toDuration(const std::string &str) {
        static const RE2 kPattern(R"(^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*)");

        double value;
        std::string unit;
        if (!RE2::FullMatch(str, kPattern, &value, &unit)) {
            POLLUX_USER_FAIL("Invalid duration '{}'", str);
        }
        if (unit == "ns") {
            return std::chrono::duration<double, std::nano>(value);
        } else if (unit == "us") {
            return std::chrono::duration<double, std::micro>(value);
        } else if (unit == "ms") {
            return std::chrono::duration<double, std::milli>(value);
        } else if (unit == "s") {
            return std::chrono::duration<double>(value);
        } else if (unit == "m") {
            return std::chrono::duration<double, std::ratio<60> >(value);
        } else if (unit == "h") {
            return std::chrono::duration<double, std::ratio<60 * 60> >(value);
        } else if (unit == "d") {
            return std::chrono::duration<double, std::ratio<60 * 60 * 24> >(value);
        }
        POLLUX_USER_FAIL("Invalid duration '{}'", str);
    }

    ConfigBase &ConfigBase::set(const std::string &key, const std::string &val) {
        POLLUX_CHECK(mutable_, "Cannot set in immutable config");
        std::unique_lock<std::shared_mutex> l(mutex_);
        configs_[key] = val;
        return *this;
    }

    ConfigBase &ConfigBase::reset() {
        POLLUX_CHECK(mutable_, "Cannot reset in immutable config");
        std::unique_lock<std::shared_mutex> l(mutex_);
        configs_.clear();
        return *this;
    }

    bool ConfigBase::valueExists(const std::string &key) const {
        std::shared_lock<std::shared_mutex> l(mutex_);
        return configs_.find(key) != configs_.end();
    };

    const std::unordered_map<std::string, std::string> &ConfigBase::rawConfigs()
    const {
        POLLUX_CHECK(
            !mutable_,
            "Mutable config cannot return unprotected reference to raw configs.");
        return configs_;
    }

    std::unordered_map<std::string, std::string> ConfigBase::rawConfigsCopy()
    const {
        std::shared_lock<std::shared_mutex> l(mutex_);
        return configs_;
    }

    melon::Optional<std::string> ConfigBase::get(const std::string &key) const {
        melon::Optional<std::string> val;
        std::shared_lock<std::shared_mutex> l(mutex_);
        auto it = configs_.find(key);
        if (it != configs_.end()) {
            val = it->second;
        }
        return val;
    }
} // namespace kumo::pollux::config
